package com.netease.nie.base.mybatis.sql;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;

import com.netease.nie.base.exceptions.UnexpectedException;
import com.netease.nie.base.mybatis.common.ShardTable;
import com.netease.nie.base.mybatis.common.ShardType;
import com.netease.nie.base.utils.HashUtils;

/**
 * 
 * @author KingsHunter
 * @createDate July 12th,2016
 *
 */
public class SqlParser {
	
	private MappedStatement mappedStatement;
	
	private BoundSql boundSql;

	public SqlParser(MappedStatement mappedStatement, BoundSql boundSql) {
		this.mappedStatement = mappedStatement;
		this.boundSql = boundSql;
	}

	public String getActualSql(String originSql, ShardTable shardTable) {
		boolean validate = validateShardTable(shardTable);
		if (validate) {
			Shard shard = getSharad();
			ShardType shardType = shardTable.shardType();
			String tableName = shardTable.tableName();
			String shardBy = shardTable.shardBy();
			String newSql = "";
			switch (shardType) {
			case SAME: {
				List<Object> valueList = shard.getValues();
				int valueListSize = valueList.size();
				if (valueListSize == 1) {
					String value = valueList.get(0).toString();
					String tableIndex = value.substring(1, 3);
					String newTableName = tableName + "_" + tableIndex;
					newSql = getRealSql(originSql, newTableName);
				}
			}
			break;
			
			case HASH : {
				List<Object> valueList = shard.getValues();
				int valueListSize = valueList.size();
				Long tempValue = 0L;
				Long value = 0L;
				for (int i = 0; i < valueListSize; i++) {
					tempValue = Long.valueOf(valueList.get(i).toString());
					value += tempValue;
				}
				
				String hashConfig = shardBy.substring(1);
				int hashCode = Integer.valueOf(hashConfig);
				Long hashTemp = value % hashCode;
				Integer hash = hashTemp == 0 ? hashCode : hashTemp.intValue();
				String tableIndex = generateTableIndex(hashConfig, String.valueOf(hash));
				String newTableName = tableName + "_" + tableIndex;
				newSql = getRealSql(originSql, newTableName);
			}
			break;
			
			case RANGE : {
				
			}
			break;
			
			case HASH_RANGE : {
				
			}
			break;
			
			case STR_HASH : {
				List<Object> valueList = shard.getValues();
				int valueListSize = valueList.size();
				if (valueListSize == 1) {
					String value = valueList.get(0).toString();
					Long bkdrHash = (long) HashUtils.bkdrHash(value);
					String hashConfig = shardBy.substring(1);
					int hashCode = Integer.valueOf(hashConfig);
					Long hashTemp = bkdrHash % hashCode;
					Integer hash = hashTemp == 0 ? hashCode : hashTemp.intValue();
					String tableIndex = generateTableIndex(hashConfig, String.valueOf(hash));
					String newTableName = tableName + "_" + tableIndex;
					newSql = getRealSql(originSql, newTableName);
				}
			}
			break;
			default:
				break;
			}
			return newSql;
		}
		
		throw new UnexpectedException("validate shardTable failed");
	}
	
	public MappedStatement getMappedStatement() {
		return mappedStatement;
	}
	
	public void setMappedStatement(MappedStatement mappedStatement) {
		this.mappedStatement = mappedStatement;
	}

	public BoundSql getBoundSql() {
		return boundSql;
	}

	public void setBoundSql(BoundSql boundSql) {
		this.boundSql = boundSql;
	}
	
	private Shard getSharad() {
		Shard shard = null;
		Object parameterObject = boundSql.getParameterObject();
		if (parameterObject != null) {
			if (parameterObject.getClass().equals(Shard.class)) {
				shard = (Shard) parameterObject;
			} else {
				@SuppressWarnings("unchecked")
				Map<String, Object> paramsMap = (Map<String, Object>) parameterObject;
				for (Entry<String, Object> entry : paramsMap.entrySet()) {
					if (entry.getValue() instanceof Shard) {
						shard = (Shard) entry.getValue();
						break;
					}
				}
			}
		}
		return shard;
	}

	private boolean validateShardTable(ShardTable shardTable) {
		return true;
	}
	
	private String generateTableIndex(String hashConfig, String hashResult) {
		int hashConfigLen = hashConfig.length();
		int hashResultLen = hashResult.length();
		int diff = hashConfigLen - hashResultLen;
		String tableIndex = "";
		if (diff == 0)
			return hashResult;
		for (int i = 0; i < diff; i++) {
			tableIndex += "0";
		}
		tableIndex += hashResult;
		return tableIndex;
	}
	
	/**
	 * 1.不支持嵌套sql
	 * 2.不支持多个分表的表
	 * @param originSql
	 * @param newTableName
	 * @return
	 */
	private String getRealSql(String originSql, String newTableName) {
		String newSql = null;
		if (StringUtils.isNotBlank(originSql)) {
			int sqlOperType = getSqlOperateType(originSql);
			switch (sqlOperType) {
			case 1: {
				//SELECT
				String select = getSelectSql(originSql);
				String whereAndOther = getWhereAndOtherSql(originSql);
				String from = getFromSql(originSql, newTableName);
				newSql = select + from + whereAndOther;
			}
				break;
			case 2: {
				//INSERT
				newSql = getInsertSql(originSql, newTableName);
			}
				break;
			case 3 : {
				//UPDATE
				newSql = getUpdateSql(originSql, newTableName);
			}
				break;
			case 4 : {
				//DELETE
				newSql = getDeleteSql(originSql, newTableName);
			}
				break;
			default:
				newSql = originSql;
				break;
			}
		}
		
		return newSql;
	}
	
	private String getSelectSql(String originSql) {
		if (StringUtils.isNotBlank(originSql))
			return StringUtils.substringBefore(originSql, "FROM");
		return StringUtils.EMPTY;
	}
	
	private String getWhereAndOtherSql(String originSql) {
		if (StringUtils.isNotBlank(originSql)) {
			String where = StringUtils.substringAfter(originSql, "WHERE");
			if (StringUtils.isNotBlank(where))
				return " WHERE " + where;
		}
		return StringUtils.EMPTY;
	}
	
	private String getFromSql(String originSql, String newTableName) {
		if (StringUtils.isNotBlank(originSql)) {
			String oldTableNames = StringUtils.substringBetween(originSql, "FROM", "WHERE");
			String[] tableNameArr = newTableName.split("_");
			tableNameArr = (String[]) ArrayUtils.subarray(tableNameArr, 0, tableNameArr.length - 1);
			String tableName = StringUtils.join(tableNameArr, "_");
			return " FROM" + oldTableNames.replaceFirst(tableName, newTableName);
		}
		return StringUtils.EMPTY;
	}
	
	private String getInsertSql(String originSql, String newTableName) {
		if (StringUtils.isNotBlank(originSql)) {
			String[] tableNameArr = newTableName.split("_");
			tableNameArr = (String[]) ArrayUtils.subarray(tableNameArr, 0, tableNameArr.length - 1);
			String tableName = StringUtils.join(tableNameArr, "_");
			return originSql.replaceFirst(tableName, newTableName);
		}
		return StringUtils.EMPTY;
	}
	
	private String getUpdateSql(String originSql, String newTableName) {
		if (StringUtils.isNotBlank(originSql)) {
			String[] tableNameArr = newTableName.split("_");
			tableNameArr = (String[]) ArrayUtils.subarray(tableNameArr, 0, tableNameArr.length - 1);
			String tableName = StringUtils.join(tableNameArr, "_");
			return originSql.replaceFirst(tableName, newTableName);
		}
		return StringUtils.EMPTY;
	}
	
	private String getDeleteSql(String originSql, String newTableName) {
		if (StringUtils.isNotBlank(originSql)) {
			String[] tableNameArr = newTableName.split("_");
			tableNameArr = (String[]) ArrayUtils.subarray(tableNameArr, 0, tableNameArr.length - 1);
			String tableName = StringUtils.join(tableNameArr, "_");
			return originSql.replaceFirst(tableName, newTableName);
		}
		return StringUtils.EMPTY;
	}
	
	private int getSqlOperateType(String originSql) {
		if (StringUtils.isNotBlank(originSql)) {
			if (StringUtils.startsWithIgnoreCase(originSql, "SELECT"))
				return 1;
			else if (StringUtils.startsWithIgnoreCase(originSql, "INSERT"))
				return 2;
			else if (StringUtils.startsWithIgnoreCase(originSql, "UPDATE"))
				return 3;
			else if (StringUtils.startsWithIgnoreCase(originSql, "DELETE"))
				return 4;
		}
		return 0;
	}
}
